package utils

import (
	"bytes"
	"context"
	"errors"
	"fmt"
	"io"
	"os"
	"os/exec"
	"strings"
	"syscall"
	"time"
)

// CmdOptions 配置命令执行的可选项
// - Dir: 指定工作目录
// - Env: 追加/覆盖环境变量（在现有环境基础上）
// - Timeout: 超时时间（0 表示不超时）
// - Stream: 是否把输出实时写到当前进程的 Stdout/Stderr，同时也会被捕获
// - Stdin: 可选的标准输入
type CmdOptions struct {
	Dir     string
	Env     map[string]string
	Timeout time.Duration
	Stream  bool
	Stdin   io.Reader
}

// CmdDo 保持向后兼容的简单封装：实时输出 + 失败时返回错误
func CmdDo(ctx context.Context,name string, ops ...string,) error {
	return CmdDoWithOptions(ctx, name, &CmdOptions{Stream: true}, ops...)
}

// CmdDoWithTimeout 在给定超时时间内执行命令
func CmdDoWithTimeout(ctx context.Context,timeout time.Duration, name string, ops ...string) error {
	var cancel context.CancelFunc
	if timeout > 0 {
		ctx, cancel = context.WithTimeout(ctx, timeout)
		defer cancel()
	}
	return CmdDoWithOptions(ctx, name, &CmdOptions{Stream: true, Timeout: timeout}, ops...)
}

// CmdDoWithOptions 使用更灵活的选项执行命令
func CmdDoWithOptions(ctx context.Context, name string, opts *CmdOptions, ops ...string) error {
	if ctx == nil {
		ctx = context.Background()
	}

	// 应用超时
	if opts != nil && opts.Timeout > 0 {
		var cancel context.CancelFunc
		ctx, cancel = context.WithTimeout(ctx, opts.Timeout)
		defer cancel()
	}

	cmd := exec.CommandContext(ctx, name, ops...)

	// 工作目录
	if opts != nil && opts.Dir != "" {
		cmd.Dir = opts.Dir
	}

	// 环境变量
	if opts != nil && len(opts.Env) > 0 {
		env := os.Environ()
		for k, v := range opts.Env {
			env = append(env, fmt.Sprintf("%s=%s", k, v))
		}
		cmd.Env = env
	}

	if opts != nil && opts.Stdin != nil {
		cmd.Stdin = opts.Stdin
	}

	// 同时捕获与可选流式输出
	var stdoutBuf, stderrBuf bytes.Buffer
	var outW io.Writer = &stdoutBuf
	var errW io.Writer = &stderrBuf
	if opts == nil || opts.Stream {
		outW = io.MultiWriter(os.Stdout, &stdoutBuf)
		errW = io.MultiWriter(os.Stderr, &stderrBuf)
	}
	cmd.Stdout = outW
	cmd.Stderr = errW

	if err := cmd.Run(); err != nil {
		// 获取退出码
		exitCode := -1
		var ee *exec.ExitError
		if errors.As(err, &ee) {
			if ws, ok := ee.Sys().(syscall.WaitStatus); ok {
				exitCode = ws.ExitStatus()
			}
		}

		combined := strings.TrimSpace(strings.Join([]string{stdoutBuf.String(), stderrBuf.String()}, "\n"))
		if errors.Is(ctx.Err(), context.DeadlineExceeded) {
			return fmt.Errorf("command %s %v timeout after %s (exit=%d), output: %s", name, ops, opts.Timeout, exitCode, combined)
		}
		return fmt.Errorf("command %s %v failed (exit=%d): %v, output: %s", name, ops, exitCode, err, combined)
	}
	return nil
}

// CmdOutput 运行命令并返回 stdout/stderr 文本（不实时输出）
func CmdOutput(ctx context.Context, name string, args ...string) (stdout, stderr string, err error) {
	if ctx == nil {
		ctx = context.Background()
	}
	cmd := exec.CommandContext(ctx, name, args...)
	var outBuf, errBuf bytes.Buffer
	cmd.Stdout = &outBuf
	cmd.Stderr = &errBuf
	runErr := cmd.Run()
	return outBuf.String(), errBuf.String(), runErr
}
